/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-21     chenhy       the first version
 */
#include "board.h"
#include "mem.h"
#include <stdlib.h>
#include <string.h>

int32_t flash_erase_pages(uint32_t page_address, uint32_t num_pages)
{
  FLASH_EraseInitTypeDef erase_init;
  int32_t page_error = 0;
  HAL_FLASH_Unlock();
  erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
  erase_init.PageAddress = page_address;
  erase_init.NbPages = num_pages;
  HAL_FLASHEx_Erase(&erase_init, (uint32_t*)&page_error);
  HAL_FLASH_Lock();
  return page_error;
}

int32_t flash_erase(uint32_t page_address)
{
  if(page_address%FLASH_PAGE_SIZE) return EXIT_FAILURE;
  if(flash_erase_pages(page_address, 1)) return EXIT_FAILURE;
  return EXIT_SUCCESS;
}

static int32_t page_program(uint32_t address, uint16_t* pbuf, int32_t size)
{
  int32_t i, write_cnt;
  write_cnt = size;
  write_cnt >>= 1;
  HAL_FLASH_Unlock();
  for(i=0;i<write_cnt;i++) {
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, address, pbuf[i]) == HAL_OK) address += 2;   
    else break;  
  }
  HAL_FLASH_Lock();
  return (i<<1);
}

static int32_t write_no_check(uint32_t address, void* pbuf, int32_t size)
{
  int32_t write_size;
  uint16_t* buf_ptr;
  while(1) {
    buf_ptr = (uint16_t*)pbuf;
    if(address % FLASH_PAGE_SIZE) write_size = FLASH_PAGE_SIZE - address % FLASH_PAGE_SIZE; 
    else write_size = FLASH_PAGE_SIZE; 
    if(size <= write_size) write_size = size;
    if(page_program(address, buf_ptr, write_size) != write_size) break;
    size -= write_size;
    if(size) {
      pbuf += write_size;
      address += write_size;
    }
    else break;
  }
  return size;
}

int32_t flash_write(uint32_t address, void* pbuf, int32_t size)
{
  int32_t remain, write_size, i;
  uint16_t* buf_ptr;
  uint8_t buffer[FLASH_PAGE_SIZE];
  remain = size;
  if((pbuf==NULL)||(remain%2)||(address%2)) return 0;
  while(1) {
    buf_ptr = (uint16_t*)pbuf;
    if(address % FLASH_PAGE_SIZE) write_size = FLASH_PAGE_SIZE - address % FLASH_PAGE_SIZE;
    else write_size = FLASH_PAGE_SIZE;
    if(remain <= write_size) write_size = remain;
    flash_read((address-address%FLASH_PAGE_SIZE), buffer, FLASH_PAGE_SIZE);
    for(i=0;i<write_size;i++) if(buffer[address%FLASH_PAGE_SIZE+i] != 0xFF) break;
    if(i != write_size) {
      flash_erase(address-address%FLASH_PAGE_SIZE);
      memcpy(&buffer[address%FLASH_PAGE_SIZE], buf_ptr, write_size);
      if(write_no_check(address-address%FLASH_PAGE_SIZE, buffer, FLASH_PAGE_SIZE)) break;
    }
    else if(write_no_check(address, buf_ptr, write_size)) break;
    remain -= write_size;
    if(remain) {
      pbuf += write_size;
      address += write_size;
    }
    else break;
  }
  return (size-remain);
}

int32_t flash_read(uint32_t address, void* pbuf, int32_t size)
{
  if(pbuf == NULL) return 0;
  memcpy(pbuf, (void*)address, size);
  return size;
}

